/*
#
#  Copyright 2011 Matthew Jannace
#
#  This file is part of RoboCode BlackHoleAgent
#
#  RoboCode BlackHoleAgent is free software: you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  RoboCode BlackHoleAgent is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with RoboCode BlackHoleAgent. If not, see
#  <http://www.gnu.org/licenses/>.
#
 */
package com.jannaceltd.TeamBlackHoleBeer.Modules.Movement.MovementMethods;

import com.jannaceltd.TeamBlackHoleBeer.BlackHole_Abstract;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.Enemy;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.TeamMate;
import com.jannaceltd.TeamBlackHoleBeer.Utilities.MathUtils;
import com.jannaceltd.TeamBlackHoleBeer.Modules.Debug.TestPoints.TestPoint;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.BotDetails;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.VirtualBullets;
import com.jannaceltd.TeamBlackHoleBeer.Modules.Movement.Movement;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import robocode.util.Utils;

/**
 * Class that Performs Minimum Risk Movements
 *
 * @author Matthew Jannace <mjannace@gmail.com>
 * @version 1.0
 *
 * 
 * Based on the HawkOnFire Robot created by the author Ruzu
 * Original HawkOnFire Source can be found at the Link Below
 * @link http://www.robocoderepository.com/BotFiles/1575/HawkOnFireOS.java
 */
public class MinimumRiskMovement {

    private BlackHole_Abstract Agent;
    private Movement move;
    private int testPointCount = 200;
    private int reevalInterval = 25;
    /**
     * Maintains information about the Robot to avoid excessive calls to getXX() Agent Methods
     */
    protected BotDetails robot = null;
    private Rectangle2D.Double battleField;
    private Point2D.Double testPoint;
    private Point2D.Double BulletPos;
    private Point2D.Double SwarmMass;
    private Point2D.Double centerFieldPos;
    private double evaluateFunc;
    private double eval = 0.0;

    /**
     * Creates a Movement Object
     * @param Agent
     * @param move
     */
    public MinimumRiskMovement(BlackHole_Abstract Agent, Movement move) {
        this.Agent = Agent;
        this.move = move;
        this.centerFieldPos = new Point2D.Double((int) (Agent.getBattleFieldWidth() / 2), (int) (Agent.getBattleFieldHeight() / 2));
        this.battleField = new Rectangle2D.Double(30, 30, Agent.getBattleFieldWidth() - 60, Agent.getBattleFieldHeight() - 60);
    }

    /**
     * Performs a Minimum Risk Movement by Evaluating Test Point to find a
     * location of least Risk,
     *
     * This Method only reevaluates the Battle field when the robot is less then
     * 15 pixels away from the destination
     */
    public void doMinimumRiskMovement() {
        robot = new BotDetails(Agent.getMyInfo(), Agent.getBattleFieldHeight(), Agent.getBattleFieldWidth());

        double distanceToNearestobject = Agent.strategy.ClosestObjectToAgent();
        double distanceToNextDestination = (robot.getPos().distance(move.getNextDestination()) * 0.5);
        //search a new destination if I reached this one
        if (distanceToNextDestination < reevalInterval) {
            chooseNewDestination(distanceToNearestobject);
        } else {
            goToCurrentDestination(distanceToNextDestination);
        }
        robot = null;
    }

    /**
     * Performs a Minimum Risk Movement by Evaluating Test Point to find a
     * location of least Risk,
     *
     * This Method immediately reevaluates the battle field to perform an
     * Evasive Minimum Risk movement
     */
    public void doEvasiveMinimumRiskMovement() {
        robot = new BotDetails(Agent.getMyInfo(), Agent.getBattleFieldHeight(), Agent.getBattleFieldWidth());
        double distanceToNearestobject = Agent.strategy.ClosestObjectToAgent();
        double distanceToNextDestination = robot.getPos().distance(move.getNextDestination());
        //search a new destination if I reached this one

        chooseNewDestination(distanceToNearestobject);
        robot = null;
    }

    /**
     * Chooses test Point and Evaluates them
     * @param distanceToNearestobject
     */
    protected void chooseNewDestination(double distanceToNearestobject) {
        Agent.strategy.FindCenteroid();
        robot = new BotDetails(Agent.getMyInfo(), Agent.getBattleFieldHeight(), Agent.getBattleFieldWidth());

        // there should be better formulas then this one but it is basically here to increase OneOnOne performance. with more bots
        // addLast will mostly be 1
        double addLast = 1 - Math.rint(Math.pow(Math.random(), Agent.getOthers()));

        if (move.TestPoints.size() > 0) {
            move.TestPoints.clear();
        }

        for (int i = 0; i < testPointCount; i++) {

            // calculate the testPoint somewhere around the current position. 100 + 200*Math.random() proved to be good if there are
            // around 10 bots in a 1000x1000 field. but this needs to be limited this to distanceToTarget*0.8. this way the bot wont
            // run into the target (should mostly be the closest bot)
            //double lenght = distanceToNearestBot;
            testPoint = MathUtils.project(
                    /* Agent Pos */robot.getPos(),
                    /* Angle */ 2 * Math.PI * Math.random(),
                    /* Length */ Math.min(distanceToNearestobject * 0.8, 100 + 200 * Math.random()));

            if (battleField.contains(testPoint)) {
                eval = 0.0;
                if ((eval = evaluate(testPoint, addLast)) < evaluate(move.getNextDestination(), addLast)) {
                    move.setNextDestination(testPoint);
                }
                move.TestPoints.add(new TestPoint(testPoint, eval));
            }
        }
        move.setLastPosition(robot.getPos());
        goToCurrentDestination(robot.getPos().distance(move.getNextDestination()));
    }

    /**
     * Sets the Robots Direction and velocity to get the the new destination
     * @param distanceToNextDestination
     */
    protected void goToCurrentDestination(double distanceToNextDestination) {
        double angle = MathUtils.calcAngle(move.getNextDestination(), robot.getPos()) - robot.getHeadingRadians();
        double direction = 1;

        if (Math.cos(angle) < 0) {
            angle += Math.PI;
            direction = -1;
        }

        Agent.setAhead(distanceToNextDestination * direction);
        Agent.setTurnRightRadians(angle = Utils.normalRelativeAngle(angle));
        Agent.setMaxVelocity(Math.abs(angle) > 1 ? 0 : 8d);
    }

    /**
     * Controls the Evaluation of all the items to evaluate a test point against
     * @param p
     * @param addLast
     * @return eval
     */
    protected double evaluate(Point2D.Double p, double addLast) {
        evaluateFunc = 0.0;

        evaluateEnemy(p);
        evaluateStanding(p, addLast);


        //if (Agent.teamates.size() > 2) {
        //evaluateCenterField(p);
        evaluateTeamMates(p);
        evaluateTeamMatesNextLocation(p);
        //evaluateFreindlyFire(p, robot.getCtime());
        evaluateFreindlyFire(p, (robot.getCtime() + 5));
        evaluateFreindlyFire(p, (robot.getCtime() + 10));
        evaluateTarget(p);
        evaluateCentroid(p);
        //}
        return evaluateFunc;
    }

    /**
     * Evaluates the Test point on its distance from the last position destination
     * @param p
     * @param addLast
     * @return eval
     */
    protected void evaluateStanding(Point2D.Double p, double addLast) {
        // this is basically here that the bot uses more space on the battlefield.
        // In melee it is dangerous to stay somewhere too long.
        evaluateFunc += Math.max(addLast, 1) * 0.08 / Math.max(p.distanceSq(move.getLastPosition()), 1);
    }

    /**
     * Evaluates the Test point on its distance from the the centerFieldPos of the Field
     * @param p
     * @return eval
     */
    protected void evaluateCenterField(Point2D.Double p) {
        evaluateFunc += (1 + Math.abs(Math.cos(MathUtils.calcAngle(robot.getPos(), p) - MathUtils.calcAngle(centerFieldPos, p)))) / p.distance(centerFieldPos);
    }

    /**
     * Evaluates the Test point on its distance from all Enemies on the Battle Field
     * @param p
     * @return eval
     */
    protected void evaluateEnemy(Point2D.Double p) {
        for (Enemy en : Agent.enemies.values()) {
            if (en.getAlive()) {
                //eval +=((EnergyDanger * BotDanger) / AntiGravity);
                evaluateFunc += ((Math.min(en.getEnergy() / robot.getEnergy(), 2)
                        * (1 + Math.abs(Math.cos(MathUtils.calcAngle(robot.getPos(), p) - MathUtils.calcAngle(en.getPos(), p)))))
                        / p.distanceSq(en.getPos()));
            }
        }
    }

    /**
     * Evaluates the Test point on its distance from all TeamMates on the Battle Field
     * @param p
     * @return eval
     */
    protected void evaluateTeamMates(Point2D.Double p) {
        for (TeamMate tm : Agent.teamates.values()) {
            if (tm.getAlive()) {
                //eval += BotDanger / AntiGravity;
                //evaluateFunc += (1 + Math.abs(Math.cos(MathUtils.calcAngle(robot.getPos(), p) - MathUtils.calcAngle(tm.getPos(), p))))
                //        / p.distanceSq(tm.getPos());
                evaluateFunc += .08 / p.distanceSq(tm.getPos());
            }
        }
    }

    /**
     * Evaluates the Test point on its distance from all TeamMates Next Destinations on the Battle Field
     * @param p
     * @return eval
     */
    protected void evaluateTeamMatesNextLocation(Point2D.Double p) {
        for (TeamMate tm : Agent.teamatesNextLocation.values()) {
            if (tm.getAlive()) {
                //eval += BotDanger / AntiGravity;
                //evaluateFunc += (1 + Math.abs(Math.cos(MathUtils.calcAngle(robot.getPos(), p) - MathUtils.calcAngle(tm.getPos(), p))))
                //        / p.distance(tm.getPos());
                evaluateFunc += .08 / p.distanceSq(tm.getPos());
            }
        }
    }

    /**
     * Evaluates the Test point on its distance from all TeamMates Friendly Fire
     * @param p
     * @param time
     * @return eval
     */
    protected void evaluateFreindlyFire(Point2D.Double p, Long time) {
        for (VirtualBullets tmVB : Agent.friendlyFire.values()) {
            //eval += Math.pow(2, BotDanger / AntiGravity);
            BulletPos = tmVB.getBulletPosition(time);
            evaluateFunc += Math.pow(2, ((1 + Math.abs(Math.cos(MathUtils.calcAngle(robot.getPos(), p) - MathUtils.calcAngle(BulletPos, p))))
                    / p.distanceSq(BulletPos)));
        }
    }

    /**
     * Evaluates the Test point on its distance from the Team Centroid
     * @param p
     * @return eval
     */
    protected void evaluateCentroid(Point2D.Double p) {
        SwarmMass = Agent.getCenterMass();
        if (!SwarmMass.equals(robot.getPos())) {
            //eval = BotDanger / AntiGravity;
            //evaluateFunc -= ((1 + Math.abs(Math.cos(MathUtils.calcAngle(robot.getPos(), p) - MathUtils.calcAngle(SwarmMass, p))))
            //        / p.distanceSq(SwarmMass));
            evaluateFunc -= (0.001 / p.distanceSq(SwarmMass));
        }
    }

    /**
     * Evaluates the Test point on its distance from the Enemy Target
     * @param p
     * @return eval
     */
    protected void evaluateTarget(Point2D.Double p) {
        //evaluateFunc -= (((1 + Math.abs(Math.cos(MathUtils.calcAngle(robot.getPos(), p) - MathUtils.calcAngle(Agent.target.getPos(), p))))
        //        / p.distanceSq(Agent.target.getPos())) / 2);
        evaluateFunc -= (0.0005 / p.distanceSq(Agent.target.getPos()));
    }
}
